package edu.gatech.cs2340.sharks.fms.data;

/**
 * DBMgr.java
 * @author Matthew Schauer
 * 
 * Abstracts communication with the database server, converting between Java objects and SQL requests
 */

import android.content.Context;
import android.net.ConnectivityManager;
import java.io.IOException;
import java.security.MessageDigest;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import org.apache.http.HttpResponse;
import org.apache.http.NameValuePair;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.message.BasicNameValuePair;

public abstract class DBMgr
{
	/**
	 * The context from which to make Internet connections
	 */
	public static Context context;
	
	/**
	 * The full URI of the script to which to make the POST requests for actions
	 */
	private final static String actioncgi = "http://srv.262144.info/a/shark/action.cgi";
	
	/**
	 * URI of the script for debugging purposes
	 */
	private final static String debugcgi = "http://srv.262144.info/a/shark/debug.cgi";
	
	/**
	 * Whether we are in debug mode
	 */
	private static boolean debug = false;
	
	/**
	 * Temporary database name for debugging purposes
	 */
	private static String dbname = "";
	
	/**
	 * Field separator in query results
	 */
	private final static String field_sep = "\n%\n";
	
	/**
	 * Compound field separator for use in multiple-return queries
	 */
	private final static String field_sep2 = "\n%%\n";
	
	/**
	 * The user that is currently logged in
	 */
	public static User user = null;
	
	/**
	 * Cache of items currently known by the program
	 */
	private static ArrayList<Item> items = null;
	
	/**
	 * Cache of users currently known by the program
	 */
	private static ArrayList<User> users = null;
	
	/**
	 * Cache of tags currently known by the program
	 */
	private static ArrayList<Tag> tags = null; // TODO Are we updating these caches on edit and delete?
		
	/**
	 * Performs a generic POST request to the database server with the given parameters
	 * 
	 * @param params A list of name/value pairs to be sent as parameters to the database server
	 * @return A packet containing the status of the request, along with a string returned by the server if a information is being requested
	 */
	private static Packet<String> post(List<NameValuePair> params)
	{
		String content;
		try
		{
			// TODO Add timeout
			HttpClient client = new DefaultHttpClient();
			HttpPost post;
			if (debug)
			{
				params.add(new BasicNameValuePair("dbname", dbname));
				post = new HttpPost(debugcgi);
			}
			else post = new HttpPost(actioncgi);
			post.setEntity(new UrlEncodedFormEntity(params));
			HttpResponse response = client.execute(post);
			int code;
			try { code = response.getStatusLine().getStatusCode(); }
			catch (Exception e) { return new Packet<String>(Status.Code.SRV_ERR, null); }
			if (code != 200) return new Packet<String>(new Status(Status.Code.SRV_ERR, "The server returned status " + code + "."), null);
			Scanner scan = new Scanner(response.getEntity().getContent()).useDelimiter("\\A"); // Black magic and stupid Scanner tricks
			content = scan.hasNext() ? scan.next() : "";
			if (".REQ_ERR".equals(content)) return new Packet<String>(Status.Code.REQ_ERR, null);
			if (".SRV_ERR".equals(content)) return new Packet<String>(Status.Code.SRV_ERR, null);
			if (".DB_ERR".equals(content)) return new Packet<String>(Status.Code.DB_ERR, null);
		}
		catch (ClientProtocolException e) { return new Packet<String>(new Status(Status.Code.EXC_CLIPRO, e.getMessage()), null); }
		catch (IOException e) { return new Packet<String>(new Status(Status.Code.EXC_IO, e.getMessage()), null); }
		catch (Exception e) { return new Packet<String>(new Status(Status.Code.EXC, e.getMessage()), null); }
		return new Packet<String>(Status.Code.OK, content);
	}
	
	/**
	 * Switches the database manager into debug mode and requests the server to do the same
	 * 
	 * @return Whether the switch was successful
	 */
	public static Status debug_setup()
	{
		debug = true;
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "setup"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".DENIED".equals(fields[0])) return new Status(Status.Code.DENIED, "The temporary database is already in use.  Please try again.");
			if (".ERR".equals(fields[0])) return new Status(Status.Code.ERR, "There was an error setting up the test database.  Let Matthew know immediately and do not continue testing.");
			if (fields.length != 2) return new Status(Status.Code.ERR, "There was an error querying the test database.  Let Matthew know immediately and do not continue testing.");
			dbname = fields[1];
			fastCacheTags();
			fastCacheUsers();
			fastCacheItems();
			return new Status(Status.Code.OK);
		}
		return p.status;
	}
	
	/**
	 * Switches the database manager out of debug mode and requests the server to do the same
	 * 
	 * @return Whether the switch was successful
	 */
	public static Status debug_teardown()
	{
		if ("".equals(dbname)) return new Status(Status.Code.DENIED, "You never created a debugging session.");
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "teardown"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".DENIED".equals(fields[0])) return new Status(Status.Code.DENIED, "There is not a debugging session to tear down.  Are you sure you created one?");
			if (".ERR".equals(fields[0])) return new Status(Status.Code.ERR, "There was an error cleaning up the test database.  Let Matthew know immediately and do not continue testing.");
			dbname = "";
			debug = false;
			return new Status(Status.Code.OK);
		}
		return p.status;
	}
	
	/**
	 * Hashes the argument with SHA-256 and converts it to an ASCII hex string for safety
	 * 
	 * @param password The password to hash
	 * @return The hashed password, or the unhashed password if there is an exception
	 */
	private static String passhash(String password)
	{
		final char[] hex = {'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'};
		try
		{
			byte[] bytes = MessageDigest.getInstance("SHA-256").digest(password.getBytes("UTF-8"));
			char[] chars = new char[bytes.length * 2];
			for (int i = 0; i < bytes.length; i++)
			{
				int j = bytes[i] & 0xFF;
				chars[i * 2] = hex[j >>> 4];
				chars[i * 2 + 1] = hex[j & 0x0F];
			}
			return new String(chars);
		}
		catch (Exception e) { return password; } // If we can't hash the password with SHA in UTF-8, I give up
	}
	
	/**
	 * Checks for an Internet connection
	 * 
	 * @return true if there is a connection, false otherwise
	 */
	private static boolean checkconn()
	{
		if (((ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE)).getActiveNetworkInfo() == null) return false;
		return true;
	}
	
	/**
	 * Checks a date for MySQL validity (YYYY-MM-DD)
	 * 
	 * @param date The date string to check
	 * @return true iff the date is valid
	 */
	private static boolean checkdate(String date)
	{
		return date.matches("^\\d{4}-\\d{2}-\\d{2}$");
	}
	
	/**
	 * Gets all items from the database in one request and caches them locally
	 * 
	 * @return Whether the request succeeded
	 */
	private static boolean fastCacheItems()
	{
		items = new ArrayList<Item>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "fast_items"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] list = p.value.split(field_sep2, -1);
			if (! ".OK".equals(list[0])) return false;
			for (int i = 1; i < list.length; i++)
			{
				String[] fields = list[i].split(field_sep, -1);
				if (fields.length != 8) return false;
				Item.Status status;
				if ("lost".equals(fields[6])) status = Item.Status.LOST;
				else if ("found".equals(fields[6])) status = Item.Status.FOUND;
				else if ("resolved".equals(fields[6])) status = Item.Status.RESOLVED;
				else return false;
				Item item = new Item(Integer.parseInt(fields[0]), fields[1], fields[2], fields[3], fields[4], Integer.parseInt(fields[5]), status);
				String[] taglist = fields[7].split(";");
				for (String tag : taglist) if (! "".equals(tag))
				{
					Packet<Tag> q = getTag(Integer.parseInt(tag));
					if (q.status.code() == Status.Code.OK) item.tags.add(q.value);
					else return false;
				}
				items.add(item);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Gets all users from the database in one request and caches them
	 * 
	 * @return Whether the request succeeded
	 */
	private static boolean fastCacheUsers()
	{
		users = new ArrayList<User>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "fast_users"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] list = p.value.split(field_sep2, -1);
			if (! ".OK".equals(list[0])) return false;
			for (int i = 1; i < list.length; i++)
			{
				String[] fields = list[i].split(field_sep, -1);
				if (fields.length != 7) return false;
				User.Role role;
				if ("admin".equals(fields[2])) role = User.Role.ADMIN;
				else if ("client".equals(fields[2])) role = User.Role.CLIENT;
				else return false;
				boolean valid;
				if ("valid".equals(fields[6])) valid = true;
				else if ("locked".equals(fields[6])) valid = false;
				else return false;
				User curuser = new User(fields[0], "0", fields[1], role, fields[3], fields[4], fields[5], valid);
				users.add(curuser);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Fetches all tags from the database and caches them
	 * 
	 * @return Whether the request succeeded
	 */
	private static boolean fastCacheTags()
	{
		tags = new ArrayList<Tag>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "fast_tags"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] list = p.value.split(field_sep2, -1);
			if (! ".OK".equals(list[0])) return false;
			for (int i = 1; i < list.length; i++)
			{
				String[] fields = list[i].split(field_sep, -1);
				if (fields.length != 3) return false;
				Tag tag = new Tag(Integer.parseInt(fields[0]), fields[1], fields[2]);
				tags.add(tag);
			}
			return true;
		}
		return false;
	}
	
	/**
	 * Attempts to log a user in
	 * 
	 * @param handle The handle of the user to log in
	 * @param password The password of the user logging in
	 * @return A packet containing the status of the login attempt, along with the user logged in if the attempt was successful
	 */
	public static Packet<User> login(String handle, String password)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "login"));
		params.add(new BasicNameValuePair("handle", handle));
		params.add(new BasicNameValuePair("password", passhash(password)));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".BAD_LOGIN".equals(p.value)) return new Packet<User>(Status.Code.BAD_LOGIN, null);
			if (".ACCT_LOCK".equals(p.value)) return new Packet<User>(Status.Code.ACCT_LOCK, null);
			String[] fields = p.value.split(field_sep, -1);
			if (fields.length != 8) return new Packet<User>(new Status(Status.Code.SRV_SYN, "Did not expect " + fields.length + " fields in response from server:  " + p.value), null);
			if (! ".OK".equals(fields[0])) return new Packet<User>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
			User.Role role;
			if ("admin".equals(fields[3])) role = User.Role.ADMIN;
			else if ("client".equals(fields[3])) role = User.Role.CLIENT;
			else return new Packet<User>(new Status(Status.Code.SRV_SYN, "Malformed user role \"" + fields[3] + "\" in response from server:  " + p.value), null); // TODO Can't have newlines in any field; check for that!
			boolean valid = false;
			if ("valid".equals(fields[6])) valid = true;
			user = new User(handle, fields[1], fields[2], role, fields[4], fields[5], fields[6], valid);
			if (user.role() == User.Role.CLIENT)
			{
				fastCacheTags();
				fastCacheItems();
			}
			else if (user.role() == User.Role.ADMIN)
			{
				fastCacheUsers();
			}
			return new Packet<User>(Status.Code.OK, user);
		}
		return new Packet<User>(p.status, null);
	}
	
	/**
	 * Logs a user out
	 * 
	 * @param user The user to log out
	 * @return The status of the logout attempt
	 */
	public static Status logout(User user)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "logout"));
		params.add(new BasicNameValuePair("handle", user.handle()));
		params.add(new BasicNameValuePair("session", user.session));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value))
			{
				DBMgr.user = null;
				items = null;
				users = null;
				return new Status(Status.Code.OK);
			}
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Tries to gain access to a user's information
	 * 
	 * @param handle The handle of the user being accessed
	 * @return A packet containing the status of the request, and an object representing the requested user if the request was successful
	 */
	public static Packet<User> getUser(String handle)
	{
		return getUser(handle, "");
	}
	
	/**
	 * Emulates login, given a user's name and session.
	 * 
	 * @param handle The user's handle
	 * @param session The user's session ID
	 * @return A packet containing the status of the request, and an object representing the requested user if the request was successful
	 */
	public static Packet<User> getUser(String handle, String session)
	{
		if (users != null) for (User u : users) if (u.handle().equals(handle))
		{
			u.session = session;
			return new Packet<User>(Status.Code.OK, u);
		}
		if (users == null) users = new ArrayList<User>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_get"));
		params.add(new BasicNameValuePair("handle", handle));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".NONE".equals(p.value)) return new Packet<User>(Status.Code.NONE, null);
			if (".DENIED".equals(p.value)) return new Packet<User>(Status.Code.DENIED, null);
			String[] fields = p.value.split(field_sep, -1);
			if (fields.length != 7) return new Packet<User>(new Status(Status.Code.SRV_SYN, "Did not expect " + fields.length + " fields in response from server:  " + p.value), null);
			if (".OK".equals(fields[0]))
			{
				User.Role role;
				if ("admin".equals(fields[2])) role = User.Role.ADMIN;
				else if ("client".equals(fields[2])) role = User.Role.CLIENT;
				else return new Packet<User>(new Status(Status.Code.SRV_SYN, "Malformed user role \"" + fields[2] + "\" in response from server:  " + p.value), null); // TODO Can't have newlines in any field; check for that!
				boolean valid;
				if ("valid".equals(fields[6])) valid = true;
				else if ("locked".equals(fields[6])) valid = false;
				else return new Packet<User>(Status.Code.SRV_SYN, null);
				User curuser = new User(handle, session, fields[1], role, fields[3], fields[4], fields[5], valid);
				users.add(curuser);
				return new Packet<User>(Status.Code.OK, curuser);
			}
			return new Packet<User>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<User>(p.status, null);
	}
	
	/**
	 * Creates a user in the database with the given parameters (method signature incomplete)
	 * 
	 * @param handle The handle of the user to create.  1-20 ASCII uppercase/lowercase letters, digits, underscore
	 * @param password The user's password
	 * @param name The user's friendly name
	 * @param role Whether the user is a client or admin
	 * @param email The user's email address
	 * @param location A string describing where the user is located
	 * @param profile An introduction to display on the user's profile page
	 * @return A status representing the success of the request
	 */
	public static Status createUser(String handle, String password, String name, User.Role role, String email, String location, String profile)
	{
		if (handle.length() < 1 || handle.length() > 20) return new Status(Status.Code.BAD_ARG, "Your chosen username is too long or too short.  It must be from 1 to 20 characters long.");
		//if (! handle.matches("/^[a-zA-Z0-9_]+$/")) return new Status(Status.Code.BAD_ARG, "Your username contains invalid characters.  Please restrict your username to letters, digits, and underscores."); // FIXME Checking fails
		if (password.length() < 1) return new Status(Status.Code.BAD_ARG, "Your password must be at least one character long.");
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		String type;
		if (role == User.Role.ADMIN) type = "admin";
		else if (role == User.Role.CLIENT) type = "client";
		else return new Status(Status.Code.ERR);
		params.add(new BasicNameValuePair("action", "user_create"));
		params.add(new BasicNameValuePair("handle", handle));
		params.add(new BasicNameValuePair("password", passhash(password)));
		params.add(new BasicNameValuePair("name", name));
		params.add(new BasicNameValuePair("type", type));
		params.add(new BasicNameValuePair("email", email));
		params.add(new BasicNameValuePair("location", location));
		params.add(new BasicNameValuePair("profile", profile));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".EXISTS".equals(p.value)) return new Status(Status.Code.EXISTS);
			if (".BAD_ARG".equals(p.value)) return new Status(Status.Code.BAD_ARG, "Your parameters contain an invalid input.  Please check the input limiations and try again.");
			return new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Changes the parameters of the specified user to match those contained in the user object (unimplemented)
	 * 
	 * @param requester The user requesting the modification
	 * @param user The user being modified, with the changes already made
	 * @return The status of the request
	 */
	public static Status editUser(User requester, User user)
	{
		return new Status(Status.Code.UNIMPL);
	}
	
	/**
	 * Updates the user's password (unimplemented)
	 * 
	 * @param user The user whose password to update
	 * @param oldpass The user's old password
	 * @param newpass The user's new password
	 * @return The status of the password change request
	 */
	public static Status changePassword(User user, String oldpass, String newpass)
	{
		if (true) return new Status(Status.Code.UNIMPL); // STUPID JAVA HAS NO RIGHT TO ERROR ON UNREACHABLE CODE.
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_chpass"));
		params.add(new BasicNameValuePair("handle", user.handle()));
		params.add(new BasicNameValuePair("password", passhash(oldpass)));
		params.add(new BasicNameValuePair("new", passhash(newpass)));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".BAD_LOGIN".equals(p.value)) return new Status(Status.Code.BAD_LOGIN, "Your old password was incorrect.  Please try again.");
			if (".BAD_ARG".equals(p.value)) return new Status(Status.Code.BAD_ARG, "Your parameters contain an invalid input.  Please check the input limiations and try again.");
			return new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Resets the user's password (unimplemented)
	 * 
	 * @param requester The user who is requesting the password reset
	 * @param user The user whose password to reset
	 * @return The status of the request
	 */
	public static Status resetPassword(User requester, User user)
	{
		if (true) return new Status(Status.Code.UNIMPL);
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_resetpass"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("user", user.handle()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".BAD_ARG".equals(p.value)) return new Status(Status.Code.BAD_ARG, "Your parameters contain an invalid input.  Please check the input limiations and try again.");
			return new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Deletes the specified user from the system
	 * 
	 * @param requester The user requesting the deletion
	 * @param user The user to delete
	 * @return The status of the deletion
	 */
	public static Status deleteUser(User requester, User user)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_delete"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("handle", user.handle()));
		// TODO Requester info
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Locks a user account
	 * 
	 * @param requester The user requesting the lock
	 * @param user The user whose account to lock
	 * @return The status of the lock request
	 */
	public static Status lockUser(User requester, User user)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_lock"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("handle", user.handle()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value))
			{
				user.valid = false;
				return new Status(Status.Code.OK);
			}
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Unlocks a user account
	 * 
	 * @param requester The user requesting the unlock
	 * @param user The user whose account to unlock
	 * @return The status of the unlock request
	 */
	public static Status unlockUser(User requester, User user)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_unlock"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("handle", user.handle()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Returns a list of all known clients
	 * 
	 * @return A packet containing a List of all clients in the system.
	 */
	public static Packet<List<User>> allUsers()
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "user_all"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".OK".equals(fields[0]))
			{
				if (fields.length < 1) return new Packet<List<User>>(Status.Code.OK, new ArrayList<User>());
				List<User> curusers = new ArrayList<User>();
				for (int i = 1; i < fields.length; i++)
				{
					Packet<User> q = getUser(fields[i]);
					if (q.status.code() != Status.Code.OK) return new Packet<List<User>>(q.status, null);
					curusers.add(q.value);
				}
				return new Packet<List<User>>(Status.Code.OK, curusers);
			}
			return new Packet<List<User>>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<List<User>>(p.status, null);
	}
	
	/**
	 * Gets the tags on an item
	 * 
	 * @param item The item whose tags to retrieve
	 * @return A packet containing a List of the tags on the item, as well as the status of the request
	 */
	public static Packet<List<Tag>> getTags(Item item)
	{
		if (items == null) items = new ArrayList<Item>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "item_tags"));
		params.add(new BasicNameValuePair("item", "" + item.id()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".OK".equals(fields[0]))
			{
				List<Tag> list = new ArrayList<Tag>();
				if (fields.length < 1) return new Packet<List<Tag>>(Status.Code.OK, new ArrayList<Tag>());
				for (int i = 1; i < fields.length; i++)
				{
					Packet<Tag> q = getTag(Integer.parseInt(fields[i]));
					if (q.status.code() != Status.Code.OK) return new Packet<List<Tag>>(q.status, null);
					list.add(q.value);
				}
				return new Packet<List<Tag>>(Status.Code.OK, list);
			}
			return new Packet<List<Tag>>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<List<Tag>>(p.status, null);
	}
	
	/**
	 * Gets an item from the database
	 * 
	 * @param id The ID of the item to retrieve
	 * @return A packet containing the status of the retrieval request and the item, if the request was successful
	 */
	public static Packet<Item> getItem(int id)
	{
		if (items != null) for (Item item : items) if (item.id() == id) return new Packet<Item>(Status.Code.OK, item);
		if (items == null) items = new ArrayList<Item>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "item_get"));
		params.add(new BasicNameValuePair("id", "" + id));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".NONE".equals(p.value)) return new Packet<Item>(Status.Code.NONE, null);
			String[] fields = p.value.split(field_sep, -1);
			if (fields.length != 7) return new Packet<Item>(new Status(Status.Code.SRV_SYN, "Did not expect " + fields.length + " fields in response from sever:  " + p.value), null);
			if (".OK".equals(fields[0]))
			{
				Item.Status status;
				if ("lost".equals(fields[6])) status = Item.Status.LOST;
				else if ("found".equals(fields[6])) status = Item.Status.FOUND;
				else if ("resolved".equals(fields[6])) status = Item.Status.RESOLVED;
				else return new Packet<Item>(Status.Code.SRV_SYN, null);
				Item item = new Item(id, fields[1], fields[2], fields[3], fields[4], Integer.parseInt(fields[5]), status);
				Packet<List<Tag>> curtags = getTags(item);
				if (curtags.status.code() != Status.Code.OK) return new Packet<Item>(curtags.status, null);
				for (Tag tag : curtags.value) item.tags.add(tag);
				items.add(item);
				return new Packet<Item>(Status.Code.OK, item);
			}
			return new Packet<Item>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<Item>(p.status, null);
	}
	
	/**
	 * Creates the item with the given parameters (incomplete)
	 * 
	 * @param owner The owner of the item to be created
	 * @param item The item to create
	 * @return The status of the item creation
	 */
	public static Status createItem(User owner, Item item)
	{
		if (items == null) items = new ArrayList<Item>();
		if (! checkdate(item.date)) return new Status(Status.Code.BAD_ARG, "The date is incorrectly formatted.");
		String status = "resolved";
		if (item.status == Item.Status.FOUND) status = "found";
		else if (item.status == Item.Status.LOST) status = "lost";
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "item_create"));
		params.add(new BasicNameValuePair("name", item.name));
		params.add(new BasicNameValuePair("date", item.date));
		params.add(new BasicNameValuePair("location", item.location));
		params.add(new BasicNameValuePair("description", item.description));
		params.add(new BasicNameValuePair("reward", "" + item.reward));
		params.add(new BasicNameValuePair("status", status));
		params.add(new BasicNameValuePair("owner", owner.handle())); // TODO Need error messages for ALL functions if null parameters are provided
		String tagstring = "";
		boolean start = true;
		for (Tag tag : item.tags)
		{
			if (! start) tagstring += ";";
			tagstring += tag.id;
			start = false;
		}
		params.add(new BasicNameValuePair("tags", tagstring));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value))
			{
				items.add(item);
				return new Status(Status.Code.OK);
			}
			if (".EXISTS".equals(p.value)) return new Status(Status.Code.EXISTS);
			if (".BAD_ARG".equals(p.value)) return new Status(Status.Code.BAD_ARG, "Your parameters contain an invalid input.  Please check the input limiations and try again.");
			return new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Edits an item
	 * 
	 * @param requester The user requesting the modification
	 * @param item The item to update, with its fields updated
	 * @return The status of the change
	 */
	public static Status editItem(User requester, Item item)
	{
		if (! checkdate(item.date)) return new Status(Status.Code.BAD_ARG, "The date is incorrectly formatted.");
		String status;
		if (item.status == Item.Status.LOST) status = "lost";
		else if (item.status == Item.Status.FOUND) status = "found";
		else if (item.status == Item.Status.RESOLVED) status = "resolved";
		else return new Status(Status.Code.ERR);
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "item_edit"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("id", "" + item.id()));
		params.add(new BasicNameValuePair("name", item.name));
		params.add(new BasicNameValuePair("date", item.date));
		params.add(new BasicNameValuePair("location", item.location));
		params.add(new BasicNameValuePair("description", item.description));
		params.add(new BasicNameValuePair("status", status));
		params.add(new BasicNameValuePair("reward", "" + item.reward));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Deletes an item from the system
	 * 
	 * @param requester The user requesting the deletion
	 * @param item The item to delete
	 * @return The status of the deletion attempt
	 */
	public static Status deleteItem(User requester, Item item) // Only admins can remove items
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "item_delete"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("id", "" + item.id()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Retrieves a tag from the database by its ID
	 * 
	 * @param id The ID of the tag to get
	 * @return A packet containing the status of the request as well as the tag
	 */
	public static Packet<Tag> getTag(int id)
	{
		if (tags != null) for (Tag tag : tags) if (tag.id == id) return new Packet<Tag>(Status.Code.OK, tag);
		if (tags == null) tags = new ArrayList<Tag>();
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tag_get"));
		params.add(new BasicNameValuePair("id", "" + id));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (fields.length != 3) return new Packet<Tag>(new Status(Status.Code.SRV_SYN, "Did not expect " + fields.length + " fields in response from sever:  " + p.value), null);
			if (".OK".equals(fields[0]))
			{
				Tag tag = new Tag(id, fields[1], fields[2]);
				tags.add(tag);
				return new Packet<Tag>(Status.Code.OK, tag);
			}
			if (".NONE".equals(p.value)) return new Packet<Tag>(Status.Code.NONE, null);
			return new Packet<Tag>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<Tag>(p.status, null);
	}
	
	/**
	 * Adds a tag to an item
	 * 
	 * @param requester The user requesting the addition
	 * @param item The item to which to add the tag
	 * @param tag The tag to add
	 * @return The status of the attempt
	 */
	public static Status addTag(User requester, Item item, Tag tag)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tag_add"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("item", "" + item.id()));
		params.add(new BasicNameValuePair("tag", "" + tag.id));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Removes a tag from an item
	 * 
	 * @param requester The user requesting the removal
	 * @param item The item from which to remove the tag
	 * @param tag The tag to remove
	 * @return The status of the removal attempt
	 */
	public static Status removeTag(User requester, Item item, Tag tag)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tag_remove"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("item", "" + item.id()));
		params.add(new BasicNameValuePair("tag", "" + tag.id));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Creates a new tag
	 * 
	 * @param requester The user requesting the tag generation
	 * @param name The name for the new tag
	 * @param description The description for the new tag
	 * @return The status of the creation attempt
	 */
	public static Status createTag(User requester, String name, String description)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tag_create"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("name", "" + name));
		params.add(new BasicNameValuePair("description", description));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			//fastCacheTags();
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Deletes a tag
	 * 
	 * @param requester The user requesting the tag deletion
	 * @param tag The tag to delete
	 * @return The status of the deletion attempt
	 */
	public static Status deleteTag(User requester, Tag tag)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tag_delete"));
		params.add(new BasicNameValuePair("request_handle", requester.handle()));
		params.add(new BasicNameValuePair("request_session", requester.session));
		params.add(new BasicNameValuePair("id", "" + tag.id));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			if (".OK".equals(p.value)) return new Status(Status.Code.OK);
			if (".NONE".equals(p.value)) return new Status(Status.Code.NONE);
			if (".DENIED".equals(p.value)) return new Status(Status.Code.DENIED);
			return new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value);
		}
		return p.status;
	}
	
	/**
	 * Gets a list of all items owned by the specified user
	 * 
	 * @param owner The owner of the items to retrieve
	 * @return A packet containing the status of the request along with an array of Item objects returned by the request
	 */
	public static Packet<List<Item>> userItems(User owner)
	{
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "search"));
		params.add(new BasicNameValuePair("owner", owner.handle()));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".OK".equals(fields[0]))
			{
				if (fields.length < 1) return new Packet<List<Item>>(Status.Code.OK, new ArrayList<Item>());
				List<Item> curitems = new ArrayList<Item>(fields.length - 1);
				for (int i = 1; i < fields.length; i++)
				{
					Packet<Item> q = getItem(Integer.parseInt(fields[i]));
					if (q.status.code() != Status.Code.OK) return new Packet<List<Item>>(q.status, null);
					curitems.add(q.value);
				}
				return new Packet<List<Item>>(Status.Code.OK, curitems);
			}
			return new Packet<List<Item>>(new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value), null);
		}
		return new Packet<List<Item>>(p.status, null);
	}
	
	/**
	 * Searches through items based on parameters.  Ignores parameters that are set to null
	 * 
	 * @param name A case-insensitive partial match on the name of the item
	 * @param location A case-insensitive partial match on the location field of the item
	 * @param startdate Only search for items lost/found after this date
	 * @param enddate Only search for items lost/found before this date
	 * @param status The status (lost, found, or resolved) of the item
	 * @param tags Tags which this item must have
	 * @return A packet containing an ArrayList of all items matching the parameters, along with the status of the request
	 */
	public static Packet<List<Item>> search(String name, String location, String startdate, String enddate, Item.Status status, List<Tag> tags)
	{
		if (startdate != null && ! checkdate(startdate)) return new Packet<List<Item>>(new Status(Status.Code.BAD_ARG, "The start date is incorrectly formatted."), null);
		if (enddate != null && ! checkdate(enddate)) return new Packet<List<Item>>(new Status(Status.Code.BAD_ARG, "The end date is incorrectly formatted."), null);
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "filter"));
		if (name != null) params.add(new BasicNameValuePair("name", name));
		if (location != null) params.add(new BasicNameValuePair("location", location));
		if (startdate != null) params.add(new BasicNameValuePair("startdate", startdate));
		if (enddate != null) params.add(new BasicNameValuePair("enddate", enddate));
		if (status != null)
		{
			String statstr = "";
			if (status == Item.Status.LOST) statstr = "lost";
			else if (status == Item.Status.FOUND) statstr = "found";
			else if (status == Item.Status.RESOLVED) statstr = "resolved";
			params.add(new BasicNameValuePair("status", statstr));
		}
		if (tags != null && tags.size() > 0)
		{
			String tagstr = "";
			for (int i = 0; i < tags.size(); i++)
			{
				tagstr += tags.get(i).name;
				if (i < tags.size() - 1) tagstr += ";";
			}
			params.add(new BasicNameValuePair("tags", tagstr));
		}
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".OK".equals(fields[0]))
			{
				if (fields.length < 2) return new Packet<List<Item>>(Status.Code.OK, new ArrayList<Item>());
				List<Item> curitems = new ArrayList<Item>(fields.length - 1);
				for (int i = 1; i < fields.length; i++)
				{
					Packet<Item> q = getItem(Integer.parseInt(fields[i]));
					if (q.status.code() != Status.Code.OK) return new Packet<List<Item>>(q.status, null);
					curitems.add(q.value);
				}
				return new Packet<List<Item>>(Status.Code.OK, curitems);
			}
			return new Packet<List<Item>>(new Status(Status.Code.SRV_SYN, "Invalid response from server:  " + p.value), null);
		}
		return new Packet<List<Item>>(p.status, null);
	}
	
	/**
	 * Gets all tags in the database
	 * 
	 * @return An ArrayList of the tags in the database
	 */
	public static Packet<List<Tag>> allTags()
	{
		fastCacheTags();
		return new Packet(Status.Code.OK, tags);
		/*List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "tags"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK)
		{
			String[] fields = p.value.split(field_sep, -1);
			if (".OK".equals(fields[0]))
			{
				if (fields.length < 1) return new Packet<List<Tag>>(Status.Code.OK, new ArrayList<Tag>());
				for (int i = 1; i < fields.length; i++)
				{
					Packet<Tag> q = getTag(Integer.parseInt(fields[i]));
					if (q.status.code() != Status.Code.OK) return new Packet<List<Tag>>(q.status, null);
				}
				return new Packet<List<Tag>>(Status.Code.OK, tags);
			}
			return new Packet<List<Tag>>(new Status(Status.Code.SRV_SYN, "Invalid response from sever:  " + p.value), null);
		}
		return new Packet<List<Tag>>(p.status, null);*/
	}
	
	/**
	 * Checks that the server is responding
	 * 
	 * @return True if the server responds as expected, otherwise false.
	 */
	public static boolean ping()
	{
		if (! checkconn()) return false;
		List<NameValuePair> params = new ArrayList<NameValuePair>();
		params.add(new BasicNameValuePair("action", "ping"));
		Packet<String> p = post(params);
		if (p.status.code() == Status.Code.OK && p.value.equals("reply")) return true;
		return false;
	}
}
